//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-11-01     ZhangYihua   first version
//
// Description  : 
//################################################################################

module vrb_dly_pipe #(  // varialble delay pipeline, inherent_dly(MIN delay) is 5
parameter           DW                      = 12,
parameter           PIPE_NUM                = 10,   // PIPE_NUM>=2
parameter           VRB_DLY_RNG             = 16,   // pipe[0] to in_dat dly range [inherent_dly:inherent_dly+VRB_DLY_RNG-1]

// the following parameters are calculated automatically
parameter           FIFO_DEPTH              = VRB_DLY_RNG/2+VRB_DLY_RNG%2+2,    // '+2' for never empty or full
parameter           VRB_DLY_BW              = $clog2(VRB_DLY_RNG)
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input               [VRB_DLY_BW-1:0]        vrb_dly,    // 0: MIN delay(inherent_dly); VRB_DLY_RNG-1: MAX delay(inherent_dly+VRB_DLY_RNG-1)

input               [DW-1:0]                in_dat,
output  reg         [PIPE_NUM*DW-1:0]       pipe_dat
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          FIFO_AW                 = $clog2(FIFO_DEPTH);
localparam          FIFO_CW                 = $clog2(FIFO_DEPTH+1);  // fifo_cnt   range [0:DEPTH]
localparam          CUR_DLY_BW              = $clog2(FIFO_DEPTH*2+1+1+1);
localparam  [FIFO_AW-1:0]       ONE         = 1;

reg                                         in_cnt;
reg                 [DW-1:0]                in_wbuf;
wire                                        fifo_wen;
wire                [2*DW-1:0]              fifo_wdat;
wire                                        fifo_full;
wire                                        fifo_afull_nc;
wire                                        fifo_ren;
wire                [2*DW-1:0]              fifo_rdat;
wire                                        fifo_empty;
wire                                        fifo_aempty_nc;
wire                [FIFO_CW-1:0]           fifo_cnt;
wire                                        prefetch_cnt_nc;
wire                                        err_fifo_ilf_nc;
wire                                        err_fifo_wof_nc;
wire                                        err_fifo_ruf_nc;
reg                                         rbuf_vld;
wire                                        rbuf_wen;
reg                 [DW-1:0]                fifo_rbuf;
reg                 [PIPE_NUM*DW-1:0]       pipe_dat_c;
wire                [CUR_DLY_BW-1:0]        cur_dly;
wire                [CUR_DLY_BW-1:0]        req_dly;
wire                                        pipe_en;
wire                                        pipe_two;
wire                                        pipe_one;

//################################################################################
// main
//################################################################################

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        in_cnt <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        in_cnt <=`U_DLY ~in_cnt;
    end else
        ;
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        in_wbuf <=`U_DLY {DW{1'b0}};
    end else if (cke==1'b1) begin
        if (in_cnt==1'b0)
            in_wbuf <=`U_DLY in_dat;
        else
            ;
    end else
        ;
end

assign fifo_wen  = cke & in_cnt & (~fifo_full);
assign fifo_wdat = {in_dat, in_wbuf};   // LSB is first

tiny_sfifo #(
        .DEPTH                          (FIFO_DEPTH                     ),	// must DEPTH>=2
        .DATA_BW                        (DW*2                           ),
        .OUT_REG_EN                     (1'b1                           ) 	// read data is REG, 1'b1 means that real total number of FIFO entry is DEPTH+1
) u_fifo ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),

        .fifo_wen                       (fifo_wen                       ),
        .fifo_wdata                     (fifo_wdat                      ),
        .fifo_full                      (fifo_full                      ),	// full
        .fifo_afull                     (fifo_afull_nc                  ),	// afull

        .fifo_ren                       (fifo_ren                       ),
        .fifo_rdata                     (fifo_rdat                      ),	// zero read latency even when REG_OUT_EN, rdata is valid before ren==1'b1
        .fifo_empty                     (fifo_empty                     ),	// 1'b1: fifo_rdata is invalid; 1'b0: fifo_rdata is valid at same clock cycle
        .fifo_aempty                    (fifo_aempty_nc                 ),	// aempty

        .fifo_cnt                       (fifo_cnt                       ),	// cnt is not include prefetch_cnt
        .prefetch_cnt                   (prefetch_cnt_nc                ),	// number of data cached in out REG, not used when OUT_REG_EN

        .cfg_fifo_afull_th              (ONE                            ),	// afull_th<fifo_cnt<, range is [0:DEPTH-1]
        .cfg_fifo_aempty_th             (ONE                            ),	// 0<, range is [1:min(DEPTH,2^ADDR_BW-1)]
        .err_fifo_ilf                   (err_fifo_ilf_nc                ),	// fifo internal logic fault
        .err_fifo_wof                   (err_fifo_wof_nc                ),	// fifo write overflow
        .err_fifo_ruf                   (err_fifo_ruf_nc                )	// fifo read underflow
);

assign fifo_ren = cke & (~fifo_empty) & (pipe_two | ((~rbuf_vld) & pipe_one));

// rbuf_vld: 
//      1'b0 menas 2 valid datas (fifo_rdat only)
//      1'b1 menas 3 valid datas (fifo_rdat + fifo_rbuf)
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        rbuf_vld <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        if (pipe_one==1'b1)
            rbuf_vld <=`U_DLY ~rbuf_vld;
        else
            ;
    end else
        ;
end

assign rbuf_wen = (rbuf_vld==1'b0) ? pipe_one : pipe_two;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        fifo_rbuf <=`U_DLY {DW{1'b0}};
    end else if (cke==1'b1) begin
        if (rbuf_wen==1'b1)
            fifo_rbuf <=`U_DLY fifo_rdat[1*DW+:DW];
        else
            ;
    end else
        ;
end

always@(*) begin
    case({pipe_two, rbuf_vld})
        2'b00   : pipe_dat_c[0*DW+:DW] = fifo_rdat[0*DW+:DW];
        2'b01   : pipe_dat_c[0*DW+:DW] = fifo_rbuf;
        2'b10   : pipe_dat_c[0*DW+:DW] = fifo_rdat[1*DW+:DW];
        default : pipe_dat_c[0*DW+:DW] = fifo_rdat[0*DW+:DW];
    endcase
end

generate if (PIPE_NUM>=2) begin:G_P1
    always@(*) begin
        if (pipe_two==1'b0)
            pipe_dat_c[1*DW+:DW] = pipe_dat[0*DW+:DW];
        else if (rbuf_vld==1'b0)
            pipe_dat_c[1*DW+:DW] = fifo_rdat[0*DW+:DW];
        else
            pipe_dat_c[1*DW+:DW] = fifo_rbuf;
    end
end endgenerate

genvar g;
generate for (g=2; g<PIPE_NUM; g=g+1) begin:G_PN
    always@(*) begin
        if (pipe_two==1'b0)
            pipe_dat_c[g*DW+:DW] = pipe_dat[(g-1)*DW+:DW];
        else
            pipe_dat_c[g*DW+:DW] = pipe_dat[(g-2)*DW+:DW];
    end
end endgenerate

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        pipe_dat <=`U_DLY {PIPE_NUM*DW{1'b0}};
    end else if ((cke==1'b1) && (pipe_en==1'b1)) begin
        pipe_dat <=`U_DLY pipe_dat_c;
    end else
        ;
end

assign cur_dly = (fifo_cnt<<1) + in_cnt + rbuf_vld;
assign req_dly = vrb_dly + 2'd2;    // +2 for never empty

assign pipe_en  = (cur_dly< req_dly) ? 1'b0 : 1'b1;     // read zero for increasing FIFO_CNT
assign pipe_two = (cur_dly> req_dly) ? 1'b1 : 1'b0;     // read two for decreasing FIFO_CNT
assign pipe_one = (cur_dly==req_dly) ? 1'b1 : 1'b0;     // read one for keeping FIFO_CNT

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
